/**
  ******************************************************************************
  * @file    bootloader.c
  * @author  ashuai0110
  * @version V2.3
  * @date    2024-07-01
  * @brief   升级引导程序
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2024-01-21    ashuai0110    完成基本内容
  * 2024-03-05    ashuai0110    增加跳转至升级引导程序接口
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "bootloader.h"

/**
 * @addtogroup bootloader
 * @{
 */

/**
 * @defgroup bootloader_ bootloader_
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup bootloader_local_macros bootloader local macros
 * @{
 */

/**
 * @defgroup bootloader_check_parameters_validity bootloader check parameters validity
 * @{
 */
/* 升级标志有效性检查 */
#define IS_VALID_UPDATA_FLAG(x)                                               \
(       ((x) == BOOT_UPDATA_FLAG)           ||                                \
        ((x) == BOOT_TO_APP_FLAG)           ||                                \
        ((x) == BOOT_TO_FACTORY_FLAG))

/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/
/**
 * @defgroup bootloader_local_variables bootloader local variables
 * @{
 */

static bootloader_t bootCtrl; /*!< bootloader管理实例 */

/**
 * @}
 */

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/

/**
 * @defgroup bootloader_global_functions bootloader global functions
 * @{
 */

/**
  * @brief  bootloader初始化
  *
  * @param  flashErase : 对接flash擦除接口(用户实现)
  *
  * @param  flashRead  : 对接flash读取接口(用户实现)
  *
  * @param  flashWrite : 对接flash写入接口(用户实现)
  *
  * @retval None
  */
void boot_init(boot_flash_erase_fn_t flashErase, boot_flash_rw_fn_t flashRead, boot_flash_rw_fn_t flashWrite)
{
    ASSERT_PARAM(IS_VALID_POINT(flashErase));
    ASSERT_PARAM(IS_VALID_POINT(flashRead));
    ASSERT_PARAM(IS_VALID_POINT(flashWrite));
    
    /* 初始化各参数 */
    bootCtrl.flashEraseFn = flashErase;
    bootCtrl.flashReadFn = flashRead;
    bootCtrl.flashWriteFn = flashWrite;
    /* 读取FLASH中的内存参数 */
    bootCtrl.flashReadFn(BOOT_ROM_START_ADDR + BOOT_INFO_ADDR_OFFSET, (void *)&bootCtrl.bootInfo, sizeof(boot_info_t));
    /* 未写入过则写入初值 */
    if(0xFFFF == bootCtrl.bootInfo.appVersion) { bootCtrl.bootInfo.appVersion = 0; }
    if(0xFFFF == bootCtrl.bootInfo.bootVersion) { bootCtrl.bootInfo.bootVersion = BOOT_VERSION; }
}

/**
  * @brief  bootloader校验升级标志
  *
  * @note   进入bootloader程序检查是升级还是直接跳转
  *
  * @param  None
  *
  * @retval None
  */
void boot_check_updata_flag(void)
{
    switch(bootCtrl.bootInfo.updataFlag)
    {
        case BOOT_UPDATA_FLAG: /* 升级则不跳转 */
            break;
        case BOOT_TO_APP_FLAG: /* 跳转到应用程序 */
            boot_to_application();
            break;
        case BOOT_TO_FACTORY_FLAG: /* 跳转到备份(出厂)程序 */
            boot_to_factory();
            break;
        default: /* 无标志则默认跳转到应用程序 */
            boot_to_application();
            break;
    }
    
}

/**
  * @brief  擦除应用程序FLASH区域
  *
  * @param  appSize : 应用程序大小
  *
  * @retval 擦除状态
  *    @arg RET_OK   : 擦除成功
  *    @arg RET_ERR  : 擦除失败
  *    @arg RET_FULL : 超出APP大小
  */
uint8_t boot_erase_application(uint32_t appSize)
{
    uint8_t ret;
    
    /* 参数若超出应用程序最大大小则返回RET_FULL */
    if(4 >= appSize || BOOT_APP_SIZE_MAX < appSize) { return RET_FULL; }
    
    /* 擦除应用程序FLASH区域 */
    ret = bootCtrl.flashEraseFn(BOOT_ROM_START_ADDR + BOOT_APP_ADDR_OFFSET, appSize);
    
    return ret;
}

/**
  * @brief  编程应用程序FLASH区域
  *
  * @param  data     : 数据缓冲区
  *
  * @param  len      : 写入数量byte
  *
  * @retval 编程状态
  *    @arg RET_OK   : 编程成功
  *    @arg RET_ERR  : 编程失败
  *    @arg RET_FULL : 超出APP大小
  */
uint8_t boot_program_application(void *data, uint16_t len)
{
    uint8_t ret;
    
    /* 写入数量若超出应用程序最大大小则返回RET_FULL */
    if(BOOT_APP_SIZE_MAX < (bootCtrl.writedSize + len)) { return RET_FULL; }
    /* 编程应用程序FLASH区域 */
    ret = bootCtrl.flashWriteFn(BOOT_ROM_START_ADDR + BOOT_APP_ADDR_OFFSET + bootCtrl.writedSize, data, len);
    bootCtrl.writedSize += len; /* 记录已写入大小 */
    
    return ret;
}

/**
  * @brief  编程升级信息中的升级标志
  *
  * @note   需要升级且在MCU复位前(APP中设置)设置,以便复位后bootloader可以检测到升级标志从而启动升级
  *
  * @param  updataFlag : 升级标志 @ref boot_updata_flag
  *
  * @retval None
  */
void boot_program_updata_flag(uint32_t updataFlag)
{
    ASSERT_PARAM(IS_VALID_UPDATA_FLAG(updataFlag));
    
    bootCtrl.bootInfo.updataFlag = updataFlag; /* 赋值升级标志 */
    /* 先擦除升级信息FLASH区域 */
    bootCtrl.flashEraseFn(BOOT_ROM_START_ADDR + BOOT_INFO_ADDR_OFFSET, sizeof(boot_info_t));
    /* 再编程升级信息FLASH区域 */
    bootCtrl.flashWriteFn(BOOT_ROM_START_ADDR + BOOT_INFO_ADDR_OFFSET, (void *)&bootCtrl.bootInfo, sizeof(boot_info_t));
}

/**
  * @brief  编程升级信息中的应用程序版本
  *
  * @note   一般在APP中设置
  *
  * @param  appVersion : 应用程序版本
  *
  * @retval None
  */
void boot_program_app_version(uint16_t appVersion)
{
    bootCtrl.bootInfo.appVersion = appVersion; /* 赋值应用程序版本 */
    /* 先擦除升级信息FLASH区域 */
    bootCtrl.flashEraseFn(BOOT_ROM_START_ADDR + BOOT_INFO_ADDR_OFFSET, sizeof(boot_info_t));
    /* 再编程升级信息FLASH区域 */
    bootCtrl.flashWriteFn(BOOT_ROM_START_ADDR + BOOT_INFO_ADDR_OFFSET, (void *)&bootCtrl.bootInfo, sizeof(boot_info_t));
}

/**
  * @brief  跳转至应用程序
  *
  * @param  None
  *
  * @retval None
  */
void boot_to_application(void)
{
    uint32_t stackTop = *(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_APP_ADDR_OFFSET);

    /* 检查栈顶指针合法性 合法则跳转应用程序 */
    if((stackTop >= BOOT_RAM_START_ADDR) && (stackTop < (BOOT_RAM_START_ADDR + BOOT_RAM_TOTAL_SIZE)))
    {
        /* 写入跳转应用程序标志 */
        boot_program_updata_flag(BOOT_TO_APP_FLAG);
        /* 恢复已使用的中断和外设 */
        boot_recover_all();
        /* 将应用程序地址+4后强转为函数指针 */
        bootCtrl.jumpFn = (boot_jump_fn_t)*(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_APP_ADDR_OFFSET + 4u);
        /* 设置程序栈顶指针 */
        BOOT_SET_STACK_POINT(stackTop);
        /* 跳转应用程序 */
        bootCtrl.jumpFn();
    } /* 不合法则跳转备份(出厂)程序 */
    else
    {
        boot_to_factory();
    }
}

/**
  * @brief  跳转至备份(出厂)程序
  *
  * @param  None
  *
  * @retval None
  */
void boot_to_factory(void)
{
    /* 写入跳转备份(出厂)程序标志 */
    boot_program_updata_flag(BOOT_TO_FACTORY_FLAG);
    /* 恢复已使用的中断和外设 */
    boot_recover_all();
    /* 将备份(出厂)程序地址+4后强转为函数指针 */
    bootCtrl.jumpFn = (boot_jump_fn_t)*(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_FACTORY_ADDR_OFFSET + 4u);
    /* 设置程序栈顶指针 */
    BOOT_SET_STACK_POINT(*(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_FACTORY_ADDR_OFFSET));
    /* 跳转备份(出厂)程序 */
    bootCtrl.jumpFn();
}

/**
  * @brief  跳转至升级引导程序
  *
  * @param  None
  *
  * @retval None
  */
void boot_to_bootloader(void)
{
    /* 恢复已使用的中断和外设 */
    boot_recover_all();
    /* 将bootloader程序地址+4后强转为函数指针 */
    bootCtrl.jumpFn = (boot_jump_fn_t)*(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_ADDR_OFFSET + 4u);
    /* 设置程序栈顶指针 */
    BOOT_SET_STACK_POINT(*(volatile uint32_t *)(BOOT_ROM_START_ADDR + BOOT_ADDR_OFFSET));
    /* 跳转bootloader程序 */
    bootCtrl.jumpFn();
}

/**
  * @brief  恢复/失能已使用的外设/中断(用户实现)
  *
  * @param  None
  *
  * @retval None
  */
__WEAK void boot_recover_all(void)
{
    /* disable/deinit all used interrupt/peripheral */
    /* example:
    timer_disable();
    timer_deinit();
    uart_disable();
    uart_deinit();
    */
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
